/*
 * Copyright (C) 2011 Patrik Åkerfeldt
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.read.scriptures.widget;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.PointF;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.Scroller;

import com.read.scriptures.R;

import java.util.EnumSet;
import java.util.LinkedList;

/**
 * A horizontally scrollable {@link ViewGroup} with items populated from an {@link Adapter}. The
 * ViewFlow uses a buffer to store loaded {@link View}s in. The default size of the buffer is 3
 * elements on both sides of the currently visible {@link View}, making up a total buffer size of 3
 * * 2 + 1 = 7. The buffer size can be changed using the {@code sidebuffer} xml attribute.
 */
public class ViewFlow extends AdapterView<Adapter> {

    private static final int SNAP_VELOCITY = 1000;
    private static final int INVALID_SCREEN = -1;
    private final static int TOUCH_STATE_REST = 0;
    private final static int TOUCH_STATE_SCROLLING = 1;

    private LinkedList<View> mLoadedViews;
    private LinkedList<View> mRecycledViews;
    private int mCurrentBufferIndex;
    private int mCurrentAdapterIndex;
    private int mSideBuffer = 2;
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;
    private int mTouchState = TOUCH_STATE_REST;
    private float mLastMotionX;
    private int mTouchSlop;
    private int mMaximumVelocity;
    private int mCurrentScreen;
    private int mNextScreen = INVALID_SCREEN;
    private boolean mFirstLayout = true;
    private ViewSwitchListener mViewSwitchListener;
    private ViewLazyInitializeListener mViewInitializeListener;
    private EnumSet<LazyInit> mLazyInit = EnumSet.allOf(LazyInit.class);
    private Adapter mAdapter;
    private int mLastScrollDirection;
    private AdapterDataSetObserver mDataSetObserver;
    private FlowIndicator mIndicator;
    private int mLastOrientation = -1;
    /**
     * Extra return value from obtainView: tells you whether the item it returned on the last call
     * was recycled rather than created by the adapter. This is a member because getting a second
     * return value requires an allocation.
     */
    private boolean mLastObtainedViewWasRecycled = false;

    private int mValideCount;
    private long mTimeSpan = 3000;

    private Handler mHandler = new Handler();
    private SwitchThread mSwitchThread;

    private OnGlobalLayoutListener orientationChangeListener = new OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            getViewTreeObserver().removeGlobalOnLayoutListener(orientationChangeListener);
            setSelection(mCurrentAdapterIndex);
        }
    };

    /**
     * Receives call backs when a new {@link View} has been scrolled to.
     */
    public static interface ViewSwitchListener {

        /**
         * This method is called when a new View has been scrolled to.
         *
         * @param view     the {@link View} currently in focus.
         * @param position The position in the adapter of the {@link View} currently in focus.
         */
        void onSwitched(View view, int position);

    }

    public static interface ViewLazyInitializeListener {

        void onViewLazyInitialize(View view, int position);
    }

    enum LazyInit {
        LEFT, RIGHT
    }

    public ViewFlow(Context context) {
        super(context);
        mSideBuffer = 3;
        init();
    }

    public ViewFlow(Context context, int sideBuffer) {
        super(context);
        mSideBuffer = sideBuffer;
        init();
    }

    public ViewFlow(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.ViewFlow);
        mSideBuffer = styledAttrs.getInt(R.styleable.ViewFlow_sidebuffer, 3);
        init();
    }

    private void init() {
        mLoadedViews = new LinkedList<View>();
        mRecycledViews = new LinkedList<View>();
        mScroller = new Scroller(getContext());
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

    public void startAutoFlowTimer() {
        if (getViewsCount() < 2) {
            return;
        }
        if (mSwitchThread != null && !mSwitchThread.isPause) {
            return;
        }
        if (mSwitchThread != null) {
            mSwitchThread.onThreadResume();
            return;
        }
        mSwitchThread = new SwitchThread();
        mSwitchThread.start();
    }

    public void stopAutoFlowTimer() {
        if (mSwitchThread != null) {
            mSwitchThread.onThreadPause();
        }
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
    }

    class SwitchThread extends Thread {

        private boolean isClose = false;
        private boolean isPause = false;
        private boolean isGoOn = true;

        /**
         * 暂停线程
         */
        public synchronized void onThreadPause() {
            isPause = true;
            isGoOn = false;
        }

        /**
         * 线程继续运行
         */
        public synchronized void onThreadResume() {
            isPause = false;
            this.notify();
        }

        /**
         * 线程等待,不提供给外部调用
         */
        private void onThreadWait() {
            try {
                synchronized (this) {
                    this.wait();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        /**
         * 关闭线程
         */
        public synchronized void closeThread() {
            try {
                this.notify();
                this.setClose(true);
                this.interrupt();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public boolean isClose() {
            return isClose;
        }

        public void setClose(boolean isClose) {
            this.isClose = isClose;
        }

        @Override
        public void run() {
            while (!isClose && !isInterrupted()) {
                if (!isPause) {
                    try {
                        Thread.sleep(mTimeSpan);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (isGoOn) {
                        mHandler.post(new Runnable() {

                            @Override
                            public void run() {
                                snapToScreen((mCurrentScreen + 1) % getChildCount());
                            }
                        });
                    } else {
                        isGoOn = true;
                    }
                } else {
                    onThreadWait();
                }
            }
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        if (newConfig.orientation != mLastOrientation) {
            mLastOrientation = newConfig.orientation;
            getViewTreeObserver().addOnGlobalLayoutListener(orientationChangeListener);
        }
    }

    public int getViewsCount() {
        return mValideCount;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int childWidth = 0;
        int childHeight = 0;
        int childState = 0;

        final int widthPadding = getWidthPadding();
        final int heightPadding = getHeightPadding();

        int count = mAdapter == null ? 0 : mAdapter.getCount();
        if (count > 0) {
            final View child = obtainView(0);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            childWidth = child.getMeasuredWidth();
            childHeight = child.getMeasuredHeight();
            mRecycledViews.add(child);
        }

        switch (widthMode) {
            case MeasureSpec.UNSPECIFIED:
                widthSize = childWidth + widthPadding;
                break;
            case MeasureSpec.AT_MOST:
                widthSize = (childWidth + widthPadding) | childState;
                break;
            case MeasureSpec.EXACTLY:
                if (widthSize < childWidth + widthPadding) {
                    widthSize |= MEASURED_STATE_TOO_SMALL;
                }
                break;
        }
        switch (heightMode) {
            case MeasureSpec.UNSPECIFIED:
                heightSize = childHeight + heightPadding;
                break;
            case MeasureSpec.AT_MOST:
                heightSize =
                        (childHeight + heightPadding) | (childState >> MEASURED_HEIGHT_STATE_SHIFT);
                break;
            case MeasureSpec.EXACTLY:
                if (heightSize < childHeight + heightPadding) {
                    heightSize |= MEASURED_STATE_TOO_SMALL;
                }
                break;
        }

        if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = heightPadding + childHeight;
        } else {
            heightSize |= (childState & MEASURED_STATE_MASK);
        }

        setMeasuredDimension(widthSize, heightSize);
    }

    private int getWidthPadding() {
        return getPaddingLeft() + getPaddingRight() + getHorizontalFadingEdgeLength() * 2;
    }

    public int getChildWidth() {
        return getWidth() - getWidthPadding();
    }

    private int getHeightPadding() {
        return getPaddingTop() + getPaddingBottom();
    }

    public int getChildHeight() {
        return getHeight() - getHeightPadding();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        final int count = getChildCount();
        for (int i = 0; i < count; ++i) {
            final View child = getChildAt(i);
            child.measure(MeasureSpec.makeMeasureSpec(getChildWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getChildHeight(), MeasureSpec.EXACTLY));
        }

        if (mFirstLayout) {
            mScroller.startScroll(0, 0, mCurrentScreen * getChildWidth(), 0, 0);
            mFirstLayout = false;
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childLeft = getPaddingLeft() + getHorizontalFadingEdgeLength();

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != View.GONE) {
                final int childWidth = child.getMeasuredWidth();
                child.layout(childLeft, getPaddingTop(), childLeft + childWidth,
                        getPaddingTop() + child.getMeasuredHeight());
                childLeft += childWidth;
            }
        }
    }

    @Override
    protected float getTopFadingEdgeStrength() {
        return 0.0f;
    }

    @Override
    protected float getBottomFadingEdgeStrength() {
        return 0.0f;
    }

    @Override
    protected float getLeftFadingEdgeStrength() {
        // always do the fading edge
        return 1.0f;
    }

    @Override
    protected float getRightFadingEdgeStrength() {
        // always do the fading edge
        return 1.0f;
    }
    /** 触摸时按下的点 **/
    PointF downP = new PointF();
    /** 触摸时当前的点 **/
    PointF curP = new PointF();
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0) {
            return false;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();
        final float x = ev.getX();

        switch (action) {
            case MotionEvent.ACTION_DOWN:
            /*
             * If being flinged and user touches, stop the fling. isFinished
			 * will be false if being flinged.
			 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                // Remember where the motion event started
                mLastMotionX = x;
                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
                stopAutoFlowTimer();
                break;

            case MotionEvent.ACTION_MOVE:
                final int deltaX = (int) (mLastMotionX - x);

                boolean xMoved = Math.abs(deltaX) > mTouchSlop;

                if (xMoved) {
                    // Scroll if the user moved far enough along the X axis
                    mTouchState = TOUCH_STATE_SCROLLING;

                    if (mViewInitializeListener != null) {
                        initializeView(deltaX);
                    }
                }

                if (mTouchState == TOUCH_STATE_SCROLLING) {
                    // Scroll to follow the motion event

                    mLastMotionX = x;

                    final int scrollX = getScrollX();
                    if (deltaX < 0) {
                        if (scrollX > 0) {
                            scrollBy(Math.max(-scrollX, deltaX), 0);
                        }
                    } else if (deltaX > 0) {
                        final int
                                availableToScroll =
                                getChildAt(getChildCount() - 1).getRight() - getPaddingRight()
                                        - getHorizontalFadingEdgeLength() - scrollX - getWidth();
                        if (availableToScroll > 0) {
                            scrollBy(Math.min(availableToScroll, deltaX), 0);
                        }
                    }
                    return true;
                }
                break;

            case MotionEvent.ACTION_UP:
                if (mTouchState == TOUCH_STATE_SCROLLING) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int velocityX = (int) velocityTracker.getXVelocity();

                    if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                        // Fling hard enough to move left
                        snapToScreen(mCurrentScreen - 1);
                    } else if (velocityX < -SNAP_VELOCITY && mCurrentScreen < getChildCount() - 1) {
                        // Fling hard enough to move right
                        snapToScreen(mCurrentScreen + 1);
                    } else {
                        snapToDestination();
                    }

                    if (mVelocityTracker != null) {
                        mVelocityTracker.recycle();
                        mVelocityTracker = null;
                    }
                } else {
                    performItemClick(mLoadedViews.get(mCurrentBufferIndex),
                            mCurrentAdapterIndex % mValideCount, 0);
                }

                mTouchState = TOUCH_STATE_REST;
                startAutoFlowTimer();
                break;
            case MotionEvent.ACTION_CANCEL:
                mTouchState = TOUCH_STATE_REST;
        }
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (getChildCount() == 0) {
            return false;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getAction();
        final float x = ev.getX();

        // 每次进行onTouch事件都记录当前的按下的坐标
        curP.x = ev.getX();
        curP.y = ev.getY();

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 记录按下时候的坐标
            // 切记不可用 downP = curP ，这样在改变curP的时候，downP也会改变
            downP.x = ev.getX();
            downP.y = ev.getY();
            // 此句代码是为了通知他的父ViewPager现在进行的是本控件的操作，不要对我的操作进行干扰
            getParent().requestDisallowInterceptTouchEvent(true);
        }

        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            // 此句代码是为了通知他的父ViewPager现在进行的是本控件的操作，不要对我的操作进行干扰
            getParent().requestDisallowInterceptTouchEvent(true);
        }

//        if (ev.getAction() == MotionEvent.ACTION_UP) {
//            // 在up时判断是否按下和松手的坐标为一个点
//            // 如果是一个点，将执行点击事件，这是我自己写的点击事件，而不是onclick
//            if (downP.x == curP.x && downP.y == curP.y) {
//                onSingleTouch();
//                return true;
//            }
//        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
            /*
             * If being flinged and user touches, stop the fling. isFinished
			 * will be false if being flinged.
			 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                // Remember where the motion event started
                mLastMotionX = x;

                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST
                        : TOUCH_STATE_SCROLLING;
                stopAutoFlowTimer();
                break;

            case MotionEvent.ACTION_MOVE:
                final int deltaX = (int) (mLastMotionX - x);

                boolean xMoved = Math.abs(deltaX) > mTouchSlop;

                if (xMoved) {
                    // Scroll if the user moved far enough along the X axis
                    mTouchState = TOUCH_STATE_SCROLLING;

                    if (mViewInitializeListener != null) {
                        initializeView(deltaX);
                    }
                }

                if (mTouchState == TOUCH_STATE_SCROLLING) {
                    // Scroll to follow the motion event

                    mLastMotionX = x;

                    final int scrollX = getScrollX();
                    if (deltaX < 0) {
                        if (scrollX > 0) {
                            scrollBy(Math.max(-scrollX, deltaX), 0);
                        }
                    } else if (deltaX > 0) {
                        final int availableToScroll = getChildAt(
                                getChildCount() - 1).getRight()
                                - getPaddingRight()
                                - getHorizontalFadingEdgeLength()
                                - scrollX
                                - getChildWidth();
                        if (availableToScroll > 0) {
                            scrollBy(Math.min(availableToScroll, deltaX), 0);
                        }
                    }
                    return true;
                }
                break;

            case MotionEvent.ACTION_UP:
                if (mTouchState == TOUCH_STATE_SCROLLING) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int velocityX = (int) velocityTracker.getXVelocity();

                    if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
                        // Fling hard enough to move left
                        snapToScreen(mCurrentScreen - 1);
                    } else if (velocityX < -SNAP_VELOCITY
                            && mCurrentScreen < getChildCount() - 1) {
                        // Fling hard enough to move right
                        snapToScreen(mCurrentScreen + 1);
                    } else {
                        snapToDestination();
                    }

                    if (mVelocityTracker != null) {
                        mVelocityTracker.recycle();
                        mVelocityTracker = null;
                    }
                } else {
                    if (mValideCount != 0) {
                        performItemClick(mLoadedViews.get(mCurrentBufferIndex),
                                mCurrentAdapterIndex % mValideCount, 0);
                    }
                }

                mTouchState = TOUCH_STATE_REST;
                startAutoFlowTimer();
                break;
            case MotionEvent.ACTION_CANCEL:
                snapToDestination();
                mTouchState = TOUCH_STATE_REST;
        }
        return true;
    }

    private void initializeView(final float direction) {
        if (direction > 0) {
            if (mLazyInit.contains(LazyInit.RIGHT)) {
                mLazyInit.remove(LazyInit.RIGHT);
                if (mCurrentBufferIndex + 1 < mLoadedViews.size()) {
                    mViewInitializeListener.onViewLazyInitialize(
                            mLoadedViews.get(mCurrentBufferIndex + 1),
                            mCurrentAdapterIndex + 1);
                }
            }
        } else {
            if (mLazyInit.contains(LazyInit.LEFT)) {
                mLazyInit.remove(LazyInit.LEFT);
                if (mCurrentBufferIndex > 0) {
                    mViewInitializeListener.onViewLazyInitialize(
                            mLoadedViews.get(mCurrentBufferIndex - 1), mCurrentAdapterIndex - 1);
                }
            }
        }
    }

    @Override
    protected void onScrollChanged(int h, int v, int oldh, int oldv) {
        super.onScrollChanged(h, v, oldh, oldv);
        if (mIndicator != null) {
            /*
             * The actual horizontal scroll origin does typically not match the
			 * perceived one. Therefore, we need to calculate the perceived
			 * horizontal scroll origin here, since we use a view buffer.
			 */
            int hPerceived = h + (mCurrentAdapterIndex % mValideCount - mCurrentBufferIndex) * getChildWidth();
            mIndicator.onScrolled(hPerceived, v, oldh, oldv);
        }
    }

    private void snapToDestination() {
        final int screenWidth = getChildWidth();
        final int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;

        snapToScreen(whichScreen);
    }

    private void snapToScreen(int whichScreen) {
        mLastScrollDirection = whichScreen - mCurrentScreen;
        if (!mScroller.isFinished()) {
            return;
        }

        whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1));

        mNextScreen = whichScreen;

        final int newX = whichScreen * getChildWidth();
        final int delta = newX - getScrollX();
        mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        } else if (mNextScreen != INVALID_SCREEN) {
            mCurrentScreen = Math.max(0, Math.min(mNextScreen, getChildCount() - 1));
            mNextScreen = INVALID_SCREEN;
            post(new Runnable() {
                @Override
                public void run() {
                    postViewSwitched(mLastScrollDirection);
                }
            });
        }
    }

    /**
     * Scroll to the {@link View} in the view buffer specified by the index.
     *
     * @param indexInBuffer Index of the view in the view buffer.
     */
    private void setVisibleView(int indexInBuffer, boolean uiThread) {
        mCurrentScreen = Math.max(0, Math.min(indexInBuffer, getChildCount() - 1));
        int dx = (mCurrentScreen * getChildWidth()) - mScroller.getCurrX();
        mScroller.startScroll(mScroller.getCurrX(), mScroller.getCurrY(), dx, 0, 0);
        if (dx == 0) {
            onScrollChanged(mScroller.getCurrX() + dx, mScroller.getCurrY(), mScroller.getCurrX() + dx, mScroller.getCurrY());
        }
        if (uiThread) {
            invalidate();
        } else {
            postInvalidate();
        }
    }

    /**
     * Set the listener that will receive notifications every time the {code ViewFlow} scrolls.
     *
     * @param l the scroll listener
     */
    public void setOnViewSwitchListener(ViewSwitchListener l) {
        mViewSwitchListener = l;
    }

    public void setOnViewLazyInitializeListener(ViewLazyInitializeListener l) {
        mViewInitializeListener = l;
    }

    @Override
    public Adapter getAdapter() {
        return mAdapter;
    }

    @Override
    public void setAdapter(Adapter adapter) {
        setAdapter(adapter, 0);
    }

    public void setAdapter(Adapter adapter, int initialPosition) {
        if (mAdapter != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        mAdapter = adapter;
        if (mAdapter != null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

        }
        if (mAdapter == null || mAdapter.getCount() == 0) {
            return;
        }

        if (mValideCount == 0) {
            mValideCount = mAdapter.getCount();
        }
        setSelection(initialPosition);
    }

    @Override
    public View getSelectedView() {
        return (mCurrentBufferIndex < mLoadedViews.size() ? mLoadedViews.get(mCurrentBufferIndex) : null);
    }

    @Override
    public int getSelectedItemPosition() {
        return mCurrentAdapterIndex;
    }

    /**
     * Set the FlowIndicator
     */
    public void setFlowIndicator(FlowIndicator flowIndicator) {
        mIndicator = flowIndicator;
        mIndicator.setViewFlow(this);
    }

    protected void recycleViews() {
        while (!mLoadedViews.isEmpty()) {
            recycleView(mLoadedViews.remove());
        }
    }

    protected void recycleView(View v) {
        if (v == null) {
            return;
        }
        mRecycledViews.addFirst(v);
        detachViewFromParent(v);
    }

    protected View getRecycledView() {
        return (mRecycledViews.isEmpty() ? null : mRecycledViews.remove());
    }

    @Override
    public void setSelection(int position) {
        mNextScreen = INVALID_SCREEN;
        mScroller.forceFinished(true);
        if (mAdapter == null) {
            return;
        }

        position = Math.max(position, 0);
        position = Math.min(position, mAdapter.getCount() - 1);

        recycleViews();

        View currentView = makeAndAddView(position, true);
        mLoadedViews.addLast(currentView);

        if (mViewInitializeListener != null) {
            mViewInitializeListener.onViewLazyInitialize(currentView, position);
        }

        for (int offset = 1; mSideBuffer - offset >= 0; offset++) {
            int leftIndex = position - offset;
            int rightIndex = position + offset;
            if (leftIndex >= 0) {
                mLoadedViews.addFirst(makeAndAddView(leftIndex, false));
            }
            if (rightIndex < mAdapter.getCount()) {
                mLoadedViews.addLast(makeAndAddView(rightIndex, true));
            }
        }

        mCurrentBufferIndex = mLoadedViews.indexOf(currentView);
        mCurrentAdapterIndex = position;

        requestLayout();
        setVisibleView(mCurrentBufferIndex, false);
        if (mIndicator != null) {
            mIndicator.onSwitched(currentView, mCurrentAdapterIndex
                    % mValideCount);
        }
        if (mViewSwitchListener != null) {
            mViewSwitchListener.onSwitched(currentView, mCurrentAdapterIndex);
        }
    }

    private void resetFocus() {
        logBuffer();
        recycleViews();
        removeAllViewsInLayout();
        mLazyInit.addAll(EnumSet.allOf(LazyInit.class));

        for (int i = Math.max(0, mCurrentAdapterIndex - mSideBuffer); i < Math.min(mAdapter.getCount(), mCurrentAdapterIndex + mSideBuffer + 1); i++) {
            mLoadedViews.addLast(makeAndAddView(i, true));
            if (i == mCurrentAdapterIndex) {
                mCurrentBufferIndex = mLoadedViews.size() - 1;
                if (mViewInitializeListener != null) {
                    mViewInitializeListener.onViewLazyInitialize(
                            mLoadedViews.getLast(), mCurrentAdapterIndex);
                }
            }
        }
        logBuffer();
        requestLayout();
    }

    private void postViewSwitched(int direction) {
        if (direction == 0) {
            return;
        }

        if (direction > 0) { // to the right
            mCurrentAdapterIndex++;
            mCurrentBufferIndex++;
            mLazyInit.remove(LazyInit.LEFT);
            mLazyInit.add(LazyInit.RIGHT);

            // Recycle view outside buffer range
            if (mCurrentAdapterIndex > mSideBuffer) {
                recycleView(mLoadedViews.removeFirst());
                mCurrentBufferIndex--;
            }

            // Add new view to buffer
            int newBufferIndex = mCurrentAdapterIndex + mSideBuffer;
            if (newBufferIndex < mAdapter.getCount()) {
                mLoadedViews.addLast(makeAndAddView(newBufferIndex, true));
            }

        } else { // to the left
            mCurrentAdapterIndex--;
            mCurrentBufferIndex--;
            mLazyInit.add(LazyInit.LEFT);
            mLazyInit.remove(LazyInit.RIGHT);

            // Recycle view outside buffer range
            if (mAdapter.getCount() - 1 - mCurrentAdapterIndex > mSideBuffer) {
                recycleView(mLoadedViews.removeLast());
            }

            // Add new view to buffer
            int newBufferIndex = mCurrentAdapterIndex - mSideBuffer;
            if (newBufferIndex > -1) {
                mLoadedViews.addFirst(makeAndAddView(newBufferIndex, false));
                mCurrentBufferIndex++;
            }

        }

        requestLayout();
        setVisibleView(mCurrentBufferIndex, true);
        if (mIndicator != null) {
            mIndicator.onSwitched(mLoadedViews.get(mCurrentBufferIndex), mCurrentAdapterIndex % mValideCount);
        }
        if (mViewSwitchListener != null) {
            mViewSwitchListener.onSwitched(mLoadedViews.get(mCurrentBufferIndex), mCurrentAdapterIndex % mValideCount);
        }
        logBuffer();
    }

    @Override
    protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
        LayoutParams lp = child.getLayoutParams();
        final int childWidthSpec = getChildMeasureSpec(parentWidthMeasureSpec, getWidthPadding(), lp.width);
        final int childHeightSpec = getChildMeasureSpec(parentHeightMeasureSpec, getHeightPadding(), lp.height);
        child.measure(childWidthSpec, childHeightSpec);
    }

    private View setupChild(View child, boolean addToEnd, boolean recycle) {
        final LayoutParams lp = child.getLayoutParams();
        child.measure(MeasureSpec.makeMeasureSpec(getChildWidth(),
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                getChildHeight(), MeasureSpec.EXACTLY));
        if (recycle) {
            attachViewToParent(child, (addToEnd ? -1 : 0), lp);
        } else {
            addViewInLayout(child, (addToEnd ? -1 : 0), lp, true);
        }
        return child;
    }

    private View makeAndAddView(int position, boolean addToEnd) {
        View view = obtainView(position);
        return setupChild(view, addToEnd, mLastObtainedViewWasRecycled);
    }

    private View obtainView(int position) {
        View convertView = getRecycledView();
        View view = mAdapter.getView(position, convertView, this);
        if (view != convertView && convertView != null) {
            mRecycledViews.add(convertView);
        }
        mLastObtainedViewWasRecycled = (view == convertView);
        LayoutParams p = view.getLayoutParams();
        if (p == null) {
            p = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            view.setLayoutParams(p);
        }
        return view;
    }

    class AdapterDataSetObserver extends DataSetObserver {

        @Override
        public void onChanged() {
            View v = getChildAt(mCurrentBufferIndex);
            if (v != null) {
                for (int index = 0; index < mAdapter.getCount(); index++) {
                    if (v.equals(mAdapter.getItem(index))) {
                        mCurrentAdapterIndex = index;
                        break;
                    }
                }
            }
            resetFocus();
        }

        @Override
        public void onInvalidated() {
            // Not yet implemented!
        }

    }

    private void logBuffer() {
    }

    public void setValidCount(int count) {
        this.mValideCount = count;
    }

    public void setTimeSpan(long timeSpan) {
        this.mTimeSpan = timeSpan;
    }

    @Override
    public boolean performItemClick(View view, int position, long id) {
        boolean handled = false;
        handled |= super.performItemClick(view, position, id);

        return handled;
    }
}
